package com.gitee.melin.bee.util;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.MissingNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.gitee.melin.bee.core.support.Result;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * huaixin 2021/12/6 5:49 PM
 */
public class JsonUtils {
    private static final Logger LOGGER = LoggerFactory.getLogger(JsonUtils.class);

    /** 默认日期时间格式 */
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";

    /** 默认日期格式 */
    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";

    /** 默认时间格式 */
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    public static ObjectMapper getInstance() {
        return OBJECT_MAPPER;
    }

    static {
        // 指定时区
        OBJECT_MAPPER.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
        // 日期类型字符串处理
        OBJECT_MAPPER.setDateFormat(new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT));

        try {
            Class<?> clazz = Class.forName("com.fasterxml.jackson.datatype.jdk8.Jdk8Module");
            OBJECT_MAPPER.registerModule((Module) clazz.newInstance());
        } catch (Throwable e) {
            LOGGER.warn("jackson-datatype-jdk8 not exist");
        }
        try {
            // java8日期处理
            JavaTimeModule javaTimeModule = new JavaTimeModule();
            javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
            javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
            javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
            javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
            javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
            javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
            OBJECT_MAPPER.registerModule(javaTimeModule);
        } catch (Throwable e) {
            LOGGER.warn("jackson-datatype-jsr310 not exist");
        }

        try {
            Class<?> clazz = Class.forName("com.fasterxml.jackson.module.scala.DefaultScalaModule$");
            Module module = (Module) clazz.getField("MODULE$").get(null);
            OBJECT_MAPPER.registerModule(module);
        } catch (Throwable e) {
            LOGGER.warn("jackson-module-scala not exist");
        }

        OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
        OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }

    /**
     * 转换为 JSON 字符串
     *
     * @param obj
     */
    public static String toJSONString(Object obj) {
        return toJSONString(obj, false);
    }

    /**
     * 转换为 JSON 字符串
     *
     * @param obj
     */
    public static String toJSONString(Object obj, boolean pretty) {
        if (obj == null) {
            return null;
        }

        try {
            if (pretty) {
                return OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
            } else {
                return OBJECT_MAPPER.writeValueAsString(obj);
            }
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 转换为 JavaBean
     *
     * @param jsonString
     */
    public static <T> Result<T> toJavaResult(String jsonString, TypeReference<Result<T>> ref) {
        try {
            OBJECT_MAPPER.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
            return OBJECT_MAPPER.readValue(jsonString, ref);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 转换为 JavaBean
     *
     * @param jsonString
     */
    public static <T> T toJavaObject(String jsonString, Class<T> clazz) {
        try {
            OBJECT_MAPPER.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
            return OBJECT_MAPPER.readValue(jsonString, clazz);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 转换为 JavaBean
     *
     * @param jsonString
     * @param ref
     * @throws IOException
     */
    public static <T> T toJavaObject(String jsonString, TypeReference<T> ref) {
        try {
            OBJECT_MAPPER.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
            return OBJECT_MAPPER.readValue(jsonString, ref);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 字符串转换为 Map
     *
     * @param jsonInput
     */
    public static <T> Map<String, T> toJavaMap(String jsonInput) {
        try {
            TypeReference<LinkedHashMap<String, T>> typeRef = new TypeReference<LinkedHashMap<String, T>>() {};
            return OBJECT_MAPPER.readValue(jsonInput, typeRef);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 字符串转换为 Map
     *
     * @param inputStream
     */
    public static <T> LinkedHashMap<String, T> toJavaMap(InputStream inputStream) {
        try {
            TypeReference<LinkedHashMap<String, T>> typeRef = new TypeReference<LinkedHashMap<String, T>>() {};
            return OBJECT_MAPPER.readValue(inputStream, typeRef);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 字符串转换为 List
     *
     * @param jsonInput
     */
    public static <T> ArrayList<T> toArrayList(String jsonInput) {
        try {
            TypeReference<ArrayList<T>> typeRef = new TypeReference<ArrayList<T>>() {};
            return OBJECT_MAPPER.readValue(jsonInput, typeRef);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 字符串转换为 List
     *
     * @param inputStream
     */
    public static <T> ArrayList<T> toArrayList(InputStream inputStream) {
        try {
            TypeReference<ArrayList<T>> typeRef = new TypeReference<ArrayList<T>>() {};
            return OBJECT_MAPPER.readValue(inputStream, typeRef);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 将 Map 转换为 Result
     *
     * @param jsonInput
     */
    public static <R> Result<R> jsonToResult(String jsonInput) {
        try {
            Map<String, Object> map = OBJECT_MAPPER.readValue(jsonInput,
                    new TypeReference<Map<String, Object>>() {});

            TypeReference<Result<R>> typeRef = new TypeReference<Result<R>>() { };
            return OBJECT_MAPPER.convertValue(map, typeRef);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public static String prettyFormat(String jsonString) {
        try {
            JsonNode rootNode = OBJECT_MAPPER.readTree(jsonString);
            return OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public static String addNodes(String jsonString, String parentPath, Map<String, Object> childNodes) {
        if (StringUtils.isBlank(parentPath)) {
            throw new IllegalArgumentException("parentPath can not blank");
        }

        try {
            JsonNode rootNode = OBJECT_MAPPER.readTree(jsonString);
            ObjectNode node = (ObjectNode) rootNode.at(parentPath);

            childNodes.forEach(node::putPOJO);
            return toJSONString(rootNode);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public static String removeNodes(String jsonString, String parentPath, String... childNodes) {
        if (StringUtils.isBlank(parentPath)) {
            throw new IllegalArgumentException("parentPath can not blank");
        }

        if (childNodes == null) {
            return jsonString;
        }

        try {
            JsonNode rootNode = OBJECT_MAPPER.readTree(jsonString);
            ObjectNode node = (ObjectNode) rootNode.at(parentPath);

            for (String key : childNodes) {
                node.remove(key);
            }

            return toJSONString(rootNode);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public static String getNodeValue(String jsonString, String path) {
        try {
            JsonNode rootNode = OBJECT_MAPPER.readTree(jsonString);
            JsonNode node = rootNode.at(path);

            if (node instanceof MissingNode) {
                return null;
            } else {
                return node.asText();
            }
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }
}
